﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System;

namespace Baiyy {
	namespace Resource {
		public class ResourceManager {
			#region types
			public class LoadItem {
                public enum LoadType {
                    AssetBundle = 1,
                    Bytes = 2,
                    Text = 3,
                    Unknown = 4
                }

				public string name;
				public int retryCount;
                public LoadType type;
                public AssetBundle bundle;
                public byte[] bytes;
                public string text;
				public bool isScript;

                public LoadItem(string name) {
                    this.name = name; retryCount = 0; type = LoadType.Unknown;
					bundle = null; bytes = null; isScript = false; text = null;

                    parse();
                }

                void parse() {
                    string[] nameParts = name.Split('.');
                    if (nameParts.Length > 0) {
                        string extName = nameParts[nameParts.Length - 1];
                        if (extName == "ab") {
                            type = LoadType.AssetBundle;
                        } else if (extName == "bytes") {
							type = LoadType.Text;

							if (nameParts [nameParts.Length - 2] == "lua") {
								isScript = true;
							}
                        }
                    }
                }
			}

			public delegate void OnItemLoading(LoadItem item);
			public delegate void OnItemLoadFailure(LoadItem item, string error);
			public delegate void OnItemLoaded(LoadItem item, int itemLoaded);
			public delegate void OnLoadFailure(List<LoadItem> failedItems);
			public delegate void OnLoadSuccess();
            #endregion

            #region vars
            static int sRetryCount = 5;

			internal int _totalItems;
			internal int _loadedItems;
			internal Hashtable _itemsTab;
			internal List<LoadItem> _loadingItems;
			internal bool _loading;
			internal bool _loadFailed;
			internal MonoBehaviour _mono;
			internal static bool _doScriptWhenLoaded;
			internal int _curVersion;

            internal OnItemLoading _itemLoading;
            internal OnItemLoadFailure _itemLoadFailure;
            internal OnItemLoaded _itemLoaded;
            internal OnLoadFailure _loadFailure;
            internal OnLoadSuccess _loadSuccess;

            internal static ResourceManager _inst;
			#endregion

			#region getter&setter
			public static ResourceManager inst {
				get {
					if (_inst == null) {
						_inst = new ResourceManager ();
					}
					return _inst;
				}
			}

            public static int retryCount { get { return sRetryCount; } set { sRetryCount = value; } }

            public int totalItemCount { get { return _totalItems;  } }
            public int loadedItemCount { get { return _loadedItems;  } }
			public static bool doScriptWhenLoaded {
				get { return _doScriptWhenLoaded; }
				set { _doScriptWhenLoaded = value; }
			}
			public int version { get { return _curVersion; } }

			public OnItemLoading onItemLoading { 
				get { return _itemLoading; } set { _itemLoading = value; } }
			public OnItemLoadFailure onItemLoadFailure { 
				get { return _itemLoadFailure; } set { _itemLoadFailure = value; } }
			public OnItemLoaded onItemLoaded { 
				get { return _itemLoaded; } set { _itemLoaded = value; } }
			public OnLoadFailure onLoadFailure { 
				get { return _loadFailure; } set { _loadFailure = value; } }
			public OnLoadSuccess onLoadSuccess { 
				get { return _loadSuccess; } set { _loadSuccess = value; } }
			#endregion

			#region methods
			public ResourceManager() {
                _totalItems = 0;
				_loadedItems = 0;
				_itemsTab = new Hashtable ();
				_loadingItems = new List<LoadItem> ();
				_loading = false;
				_loadFailed = false;
				_mono = null;
				_doScriptWhenLoaded = false;
				_curVersion = -1;

                _itemLoading = null;
                _itemLoadFailure = null;
                _itemLoaded = null;
                _loadFailure = null;
                _loadSuccess = null;
            }

			public void Init(MonoBehaviour mono) {
				_mono = mono;
			}

            /// <summary>
            /// 加载资源。
            /// </summary>
            /// <param name="versionFile">版本文件路径</param>
			public void Load(string versionFile) {
				HashtableEx verFile = new HashtableEx(
					(Hashtable)MiniJSON.JsonDecode (File.ReadAllText(versionFile)));
				ArrayList files = verFile.asArray ("files");
				_curVersion = verFile.asInt ("version");
				_totalItems += files.Count;

				foreach (object file in files) {
                    HashtableEx hTab = new HashtableEx(file as Hashtable);
					DoLoadItem (hTab.asString("name"));
				}
			}

            /// <summary>
            /// 加载资源。
            /// </summary>
            /// <param name="name">资源路径</param>
            /// <param name="cache">是否缓存资源</param>
            /// <param name="callback">加载回调函数</param>
			public void Load(string name, bool cache, Action<LoadItem, string> callback = null) {
				if (cache) {
					_totalItems += 1;
				}
				DoLoadItem (name, cache, callback);
			}

            public void Clear()
            {
                if (_loading) {
                    throw new Exception("[ResourceManager] is loading resource now");
                }

                _totalItems = 0;
                _loadedItems = 0;

                foreach(DictionaryEntry de in _itemsTab) {
                    LoadItem item = de.Value as LoadItem;
                    item.bundle.Unload(true);
                }
                _itemsTab.Clear();

                _loadingItems.Clear();
                _loading = false;
                _loadFailed = false;
            }

            void DoLoadItem(string name) {
				if (_itemsTab.ContainsKey (name)) {
					_loadedItems += 1;
                    return;
				}
				_loadingItems.Add (new LoadItem(name));

				if (!_loading) {
					_mono.StartCoroutine (RunLoad ());
				}
			}

			void DoLoadItem(string name, bool cache, Action<LoadItem, string> callback) {
				if (_itemsTab.ContainsKey (name)) {
					if (callback != null) {
						callback (_itemsTab[name] as LoadItem, null);
					}
					return;
				}
                _mono.StartCoroutine(RunLoad(new LoadItem(name), cache, callback));
			}

			string GetURL(string name) {
				string fullPath = Config.writablePath + name;
				if (!File.Exists (fullPath)) {
					fullPath = Config.streamAssetsPath + name;
				}
				return fullPath;
			}

			IEnumerator RunLoad() {
				_loading = true;
				while (!_loadFailed) {
					LoadItem loadItem;
					if (_loadingItems.Count > 0) {
						loadItem = _loadingItems [0];
						_loadingItems.RemoveAt (0);
					} else {
						break;
					}

					if (_itemLoading != null) {
						_itemLoading (loadItem);
					}

                    if (loadItem.type != LoadItem.LoadType.AssetBundle
                         && loadItem.type != LoadItem.LoadType.Text) {
                        OnItemLoadFinished(true, loadItem, null);
                        continue;
                    }
						
					string url = GetURL(loadItem.name);
					if (Application.platform != RuntimePlatform.Android) {
						url = "file:///" + url;
					}
					WWW www = new WWW (url);
					yield return www;

					if (string.IsNullOrEmpty (www.error)) {
                        switch(loadItem.type) {
                            case LoadItem.LoadType.AssetBundle:
                                loadItem.bundle = www.assetBundle;
                                break;
							case LoadItem.LoadType.Text: {
								loadItem.text = www.text;
								if (loadItem.isScript && _doScriptWhenLoaded) {
									LuaController.DoString (loadItem.text);
								}
							} break;
                        }
						OnItemLoadFinished (true, loadItem, null);
					} else {
						OnItemLoadFinished (false, loadItem, www.error);
					}
				}
				_loading = false;
			}

            IEnumerator RunLoad(LoadItem loadItem, bool cache, Action<LoadItem, string> callback)
            {
                if (loadItem.type != LoadItem.LoadType.AssetBundle
                    && loadItem.type != LoadItem.LoadType.Text) {
                    callback(loadItem, null);
                    yield return null;
                }

				string url = GetURL(loadItem.name);
                if (Application.platform != RuntimePlatform.Android)
                {
                    url = "file:///" + url;
                }
                WWW www = new WWW(url);
                yield return www;

                if (string.IsNullOrEmpty(www.error))
                {
                    switch (loadItem.type) {
                        case LoadItem.LoadType.AssetBundle:
                            loadItem.bundle = www.assetBundle;
                            break;
						case LoadItem.LoadType.Text: {
							loadItem.text = www.text;
							if (loadItem.isScript && _doScriptWhenLoaded) {
								LuaController.DoString (loadItem.text);
							}
						} break;
                    }
                    if (cache) {
                        _itemsTab[loadItem.name] = loadItem;
                        _loadedItems += 1;
                    }

                    if (callback != null) {
                        callback(loadItem, null);
                    }
                }
                else
                {
                    if (callback != null) {
                        callback(null, www.error);
                    }
                }
            }

            void OnItemLoadFinished(bool ok, LoadItem item, string error) {
				if (ok) {
					_loadedItems += 1;
					_itemsTab [item.name] = item;
					if (_itemLoaded != null) {
						_itemLoaded (item, _loadedItems);
					}

					if (_loadedItems == _totalItems) {
						if (_loadSuccess != null) {
							_loadSuccess ();
						}
					}
				} else {
					if (_itemLoadFailure != null) {
						_itemLoadFailure (item, error);
					}

					// 多次重试仍不行，加载失败
					if (item.retryCount >= sRetryCount) {
						_loadFailed = true;
						return;
					}

					// 重试
					item.retryCount += 1;
					_loadingItems.Add (item);
				}
			}

            public AssetBundle GetAssetBundle(string name) {
                LoadItem item = (LoadItem)_itemsTab[name];
                if (item != null && item.type == LoadItem.LoadType.AssetBundle) {
                    return item.bundle;
                }
                return null;
            }

            public string GetText(string name)
            {
                LoadItem item = (LoadItem)_itemsTab[name];
                if (item != null && item.type == LoadItem.LoadType.Text)
                {
                    return item.text;
                }
                return null;
            }
            #endregion
        }
	}
}
